package com.yinsin.jpabatis.session;

import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.data.domain.Page;

import com.yinsin.jpabatis.binging.BindingException;
import com.yinsin.jpabatis.config.Configuration;
import com.yinsin.jpabatis.exceptions.ExceptionFactory;
import com.yinsin.jpabatis.exceptions.JpaBatisException;
import com.yinsin.jpabatis.executor.ErrorContext;
import com.yinsin.jpabatis.executor.Executor;
import com.yinsin.jpabatis.executor.result.DefaultMapResultHandler;
import com.yinsin.jpabatis.executor.result.DefaultResultContext;
import com.yinsin.jpabatis.mapper.MappedStatement;

public class DefaultSqlSession implements SqlSession {
	private final Configuration configuration;
	private final Executor executor;
	
	public DefaultSqlSession(Configuration configuration, Executor executor) {
		this.configuration = configuration;
		this.executor = executor;
	}

	@Override
	public <T> T selectOne(String statement) {
		return this.selectOne(statement, null);
	}

	@Override
	public <T> T selectOne(String statement, Object parameter) {
		MappedStatement ms = configuration.getMappedStatement(statement);
		T object;
		try {
			object = executor.query(ms, wrapCollection(parameter), Executor.NO_RESULT_HANDLER);
		} catch (SQLException e) {
			throw new JpaBatisException(e);
		}
		return object;
	}

	@Override
	public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
		return this.selectMap(statement, null, mapKey);
	}

	@Override
	public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
		final List<? extends V> list = selectList(statement, parameter);
		final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(),
				configuration.getReflectorFactory());
		final DefaultResultContext<V> context = new DefaultResultContext<>();
		for (V o : list) {
			context.nextResultObject(o);
			mapResultHandler.handleResult(context);
		}
		return mapResultHandler.getMappedResults();
	}

	@Override
	public <E> List<E> selectList(String statement) {
		Object param = null;
		return selectList(statement, param);
	}

	@Override
	public <E> List<E> selectList(String statement, Object parameter) {
		try {
			MappedStatement ms = configuration.getMappedStatement(statement);
			return executor.queryList(ms, wrapCollection(parameter), Executor.NO_RESULT_HANDLER);
		} catch (Exception e) {
			throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
		} finally {
			ErrorContext.instance().reset();
		}
	}
	
	@Override
	public <E> Page<E> selectList(String statement, RowBounds rowBounds) {
		return selectList(statement, null, rowBounds);
	}

	@Override
	public <E> Page<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
		try {
			MappedStatement ms = configuration.getMappedStatement(statement);
			return executor.queryList(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
		} catch (Exception e) {
			throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
		} finally {
			ErrorContext.instance().reset();
		}
	}

	@Override
	public Configuration getConfiguration() {
		return configuration;
	}

	@Override
	public <T> T getMapper(Class<T> type) {
		return configuration.<T> getMapper(type, this);
	}

	@Override
	public void clearCache() {
		executor.clearLocalCache();
	}

	private Object wrapCollection(final Object object) {
		if (object instanceof Collection) {
			StrictMap<Object> map = new StrictMap<>();
			map.put("collection", object);
			if (object instanceof List) {
				map.put("list", object);
			}
			return map;
		} else if (object != null && object.getClass().isArray()) {
			StrictMap<Object> map = new StrictMap<>();
			map.put("array", object);
			return map;
		}
		return object;
	}

	public static class StrictMap<V> extends HashMap<String, V> {

		private static final long serialVersionUID = -5741767162221585340L;

		@Override
		public V get(Object key) {
			if (!super.containsKey(key)) {
				throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
			}
			return super.get(key);
		}

	}
}
